package simpleparser

import "errors"

// 词法规则
// Int: 'int'
// Id : [a-zA-Z_] ([a-zA-Z_] | [0-9])*
// IntLiteral: [0-9]+
// GT : '>'
// GE : '>='
// LT : '<'
// LE : '<='
// EQ : '=='
// NE : '!='
// Assignment : '='
// Plus : '+'
// Minus : '-'
// Star : '*'
// Slash : '/'
// LParen : '('
// RParen : ')'
// Semi : ';'

const (
	ILLEGAL TokenType = iota
	INT
	ID
	INTLITERAL
	GT
	GE
	LT
	LE
	EQ
	NE
	NOT
	ASSIGNMENT
	PLUS
	MINUS
	STAR
	SLASH
	LPAREN
	RPAREN
	SEMICOLON
)

// token 类型 对应的字符串描述
var TOKEN_TYPE_DESC = map[TokenType]string{
	ILLEGAL:    "ILLEGAL",
	INT:        "INT",
	ID:         "ID",
	INTLITERAL: "INTLITERAL",
	GT:         "GT",
	GE:         "GE",
	LT:         "LT",
	LE:         "LE",
	EQ:         "EQ",
	NE:         "NE",
	NOT:        "NOT",
	ASSIGNMENT: "ASSIGNMENT",
	PLUS:       "PLUS",
	MINUS:      "MINUS",
	STAR:       "STAR",
	SLASH:      "SLASH",
	LPAREN:     "LPAREN",
	RPAREN:     "RPAREN",
	SEMICOLON:  "SEMICOLON",
}

// 自动状态机状态
type DfaState int

const (
	STATE_INVALID DfaState = iota
	STATE_INITIAL
	STATE_ID
	STATE_INTLITERAL
	STATE_GT
	STATE_GE
	STATE_LT
	STATE_LE
	STATE_EQ
	STATE_NE
	STATE_NOT
	STATE_ASSIGNMENT
	STATE_PLUS
	STATE_MINUS
	STATE_STAR
	STATE_SLASH
	STATE_LPAREN
	STATE_RPAREN
	STATE_SEMICOLON
)

func tokenInitial(ch rune, token *Token) DfaState {
	state := STATE_INITIAL

	switch {
	case (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'):
		state = STATE_ID
		token.Value += string(ch)
		token.Type = ID

	case ch == '0' || (ch >= '1' && ch <= '9'):
		state = STATE_INTLITERAL
		token.Value += string(ch)
		token.Type = INTLITERAL

	case ch == '>':
		state = STATE_GT
		token.Value += string(ch)
		token.Type = GT
	case ch == '<':
		state = STATE_LT
		token.Value += string(ch)
		token.Type = LT
	case ch == '=':
		state = STATE_ASSIGNMENT
		token.Value += string(ch)
		token.Type = ASSIGNMENT
	case ch == '!':
		state = STATE_NOT
		token.Value += string(ch)
		token.Type = NOT

	case ch == '+':
		state = STATE_PLUS
		token.Value += string(ch)
		token.Type = PLUS
	case ch == '-':
		state = STATE_MINUS
		token.Value += string(ch)
		token.Type = MINUS
	case ch == '*':
		state = STATE_STAR
		token.Value += string(ch)
		token.Type = STAR
	case ch == '/':
		state = STATE_SLASH
		token.Value += string(ch)
		token.Type = SLASH

	case ch == '(':
		state = STATE_LPAREN
		token.Value += string(ch)
		token.Type = LPAREN
	case ch == ')':
		state = STATE_RPAREN
		token.Value += string(ch)
		token.Type = RPAREN

	case ch == ';':
		state = STATE_SEMICOLON
		token.Value += string(ch)
		token.Type = SEMICOLON

	// 空白字符	-> skip
	// 恢复初始识别状态
	case ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n':
		state = STATE_INITIAL

	default:
		state = STATE_INVALID
	}

	return state
}

func isAlpha(ch rune) bool {
	return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'
}

func isDigit(ch rune) bool {
	return ch >= '0' && ch <= '9'
}

func addToken(token *Token, tokens *TokenList, checkID bool) {
	// 优先检查是否为预定义关键字
	if checkID && token.Type == ID {
		switch token.Value {
		case "int":
			token.Type = INT
		}
	}

	tokens.Push(*token)
	token.Value = ""
}

func Tokenize(input string) (*TokenList, error) {
	state := STATE_INITIAL

	token := Token{}
	tokens := TokenList{}

	for _, ch := range input {

		switch state {
		case STATE_INITIAL:
			state = tokenInitial(ch, &token)

		case STATE_ID:
			if isAlpha(ch) || isDigit(ch) {
				token.Value += string(ch)
			} else {
				addToken(&token, &tokens, true)
				state = tokenInitial(ch, &token)
			}

		// 必须输入=才能确定是> vs >= or < vs <= or = vs == or ! vs !=
		case STATE_GT:
			if ch == '=' {
				token.Type = GE
				token.Value += string(ch)

				state = STATE_GE
			} else {
				addToken(&token, &tokens, false)
				state = tokenInitial(ch, &token)
			}

		case STATE_LT:
			if ch == '=' {
				token.Type = LE
				token.Value += string(ch)

				state = STATE_LE
			} else {
				addToken(&token, &tokens, false)
				state = tokenInitial(ch, &token)
			}

		case STATE_ASSIGNMENT:
			if ch == '=' {
				token.Type = EQ
				token.Value += string(ch)

				state = STATE_EQ
			} else {
				addToken(&token, &tokens, false)
				state = tokenInitial(ch, &token)
			}

		case STATE_NOT:
			if ch == '=' {
				token.Type = NE
				token.Value += string(ch)

				state = STATE_NE
			} else {
				addToken(&token, &tokens, false)
				state = tokenInitial(ch, &token)
			}

		case STATE_PLUS, STATE_MINUS, STATE_STAR, STATE_SLASH,
			STATE_EQ, STATE_NE, STATE_GE, STATE_LE,
			STATE_LPAREN, STATE_RPAREN, STATE_SEMICOLON:

			addToken(&token, &tokens, false)
			state = tokenInitial(ch, &token)

		case STATE_INTLITERAL:
			if isDigit(ch) {
				token.Value += string(ch)
			} else if isAlpha(ch) {
				// 数字+字母为非法id
				return nil, errors.New("id必须为字母或下划线开头")
			} else {
				addToken(&token, &tokens, true)
				state = tokenInitial(ch, &token)
			}
		}
	}

	if state != STATE_INITIAL {
		addToken(&token, &tokens, true)
	}

	return &tokens, nil
}
